package cn.com.tarotframework.config.advice;

import cn.com.tarotframework.utils.constants.TarotConstantUtils;
import cn.com.tarotframework.utils.context.TarotApplicationContextUtil;
import cn.com.tarotframework.utils.exception.TarotBusinessException;
import cn.com.tarotframework.utils.log.TarotLogUtils;
import cn.com.tarotframework.utils.response.TarotCode;
import cn.com.tarotframework.utils.response.TarotResponseNotPackResultBody;
import cn.com.tarotframework.utils.response.TarotResponseResultBody;
import cn.com.tarotframework.utils.response.TarotResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.constraints.NotNull;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;
import java.util.Set;

/**
 * copyright (C), 2022, 塔罗牌基础架构
 * @program tarot-config-spring-boot-starter
 * @description 处理架构公共配置
 * @author xincan
 * @date 2022/05/20 16:44
 * @version 0.0.1-SNAPSHOT
 */
@Slf4j
@RestControllerAdvice
public class TarotBaseExceptionControllerAdvice implements ResponseBodyAdvice<Object> {

        /**
         * @description 拦截自定义注解，确立执行 beforeBodyWrite 方法
         * @author xincan
         * @date 2022/5/17 10:20
         * @param methodParameter 方法参数对象
         * @param clazz HttpMessageConverter
         * @return boolean
         **/
        @Override
        public boolean supports(MethodParameter methodParameter, @NotNull Class<? extends HttpMessageConverter<?>> clazz) {
            boolean notPackBool = AnnotatedElementUtils.hasAnnotation(methodParameter.getContainingClass(), TarotResponseNotPackResultBody.class);
            boolean bool = AnnotatedElementUtils.hasAnnotation(methodParameter.getContainingClass(), TarotResponseResultBody.class);
            if(notPackBool && !bool) {
                return methodParameter.hasMethodAnnotation(TarotResponseResultBody.class);
            }
            if(bool && !notPackBool) {
                return !methodParameter.hasMethodAnnotation(TarotResponseNotPackResultBody.class);
            }
            return false;
        }

        /**
         * 包装控制层controller统一返回值
         * @param body 消息体
         * @param methodParameter  方法参数列表
         * @param mediaType  请求类型
         * @param clazz  请求类
         * @param request  请求对象
         * @param response 返回对象
         * @author xincan
         * @date 2021/7/20 11:57
         * @return Object
         **/
        @Override
        public Object beforeBodyWrite(Object body, @Nullable MethodParameter methodParameter, @Nullable MediaType mediaType,
            @Nullable Class<? extends HttpMessageConverter<?>> clazz,
            @Nullable ServerHttpRequest request, @Nullable ServerHttpResponse response) {
            // 如果是自定义包装返回值，则直接返回
            if (body instanceof TarotResult) {
                return body;
            }
            // 如果是String类型，则基于原生的jackson进行包装返回
            if (body instanceof String) {
                ObjectMapper objectMapper = new ObjectMapper();
                try {
                    assert response != null;
                    response.getHeaders().set("Content-Type", "application/json;charset=utf-8");
                    return objectMapper.writeValueAsString(TarotResult.success(body));
                } catch (JsonProcessingException e) {
                    throw new TarotBusinessException(TarotCode.INTERNAL_SERVER_ERROR,"返回消息体类型失败，应返回String类型的JSON");
                }
            }
            // 如果不是以上两种类型的返回结果体，则进行一次统一返回消息体封装
            return TarotResult.success(body);
        }

    /**
     * 自定义业务异常
     * @param request   请求对象
     * @param tarotBusinessException 业务异常类
     * @author xincan
     * @date 2022/9/9 14:37
     * @return TarotResult<String>
     */
    @ExceptionHandler(TarotBusinessException.class)
    public TarotResult<String> handlerBusinessException(HttpServletRequest request, TarotBusinessException tarotBusinessException) {
        String errorMessage = tarotBusinessException.getMessage();
        return TarotLogUtils.messageHandler(request, TarotCode.BUSINESS_EXCEPTION, errorMessage, tarotBusinessException);
    }

    /**
     * 捕获 202 异常处理 请求频繁
     * @param request   请求对象
     * @param undeclaredThrowableException 202 请求频繁
     * @author xincan
     * @date 2019/9/14 16:21
     * @return TarotResult<String>
     */
    @ExceptionHandler(value = UndeclaredThrowableException.class)
    public TarotResult<String> undeclaredThrowableException(HttpServletRequest request, UndeclaredThrowableException undeclaredThrowableException) {
        String errorMessage = undeclaredThrowableException.getMessage();
        return TarotLogUtils.messageHandler(request, TarotCode.REQUEST_SERVICE_ERROR, errorMessage, undeclaredThrowableException);
    }

    /**
     * 捕获 404 异常处理 请求地址不存在
     * @param request   请求对象
     * @param noHandlerFoundException 404 异常
     * @author xincan
     * @date 2019/9/14 16:21
     * @return TarotResult<String>
     */
    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public TarotResult<String> handlerNoHandlerFoundException(HttpServletRequest request, NoHandlerFoundException noHandlerFoundException) {
        return TarotLogUtils.messageHandler(request, TarotCode.NOT_FOUND, TarotConstantUtils.TAROT_EXCEPTION_LOG_ERROR_MESSAGE, noHandlerFoundException);
    }

    /**
     * 捕获 405 异常处理 是否是正确的URL请求
     * @param request   请求对象
     * @param httpRequestMethodNotSupportedException 405 异常
     * @author xincan
     * @date 2019/9/14 16:21
     * @return TarotResult<String>
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public TarotResult<String> handlerHttpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException) {
        String errorMessage = httpRequestMethodNotSupportedException.getMessage();
        return TarotLogUtils.messageHandler(request, TarotCode.METHOD_NOT_ALLOWED, errorMessage, httpRequestMethodNotSupportedException);
    }

    /**
     * 捕获 415 异常处理 服务器无法处理的请求媒体格式
     * @param request   请求对象
     * @param httpMediaTypeNotSupportedException HttpMediaTypeNotSupportedException 415 异常
     * @author xincan
     * @date 2019/9/14 16:21
     * @return TarotResult<String>
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    public TarotResult<String> handlerHttpMediaTypeNotSupportedException(HttpServletRequest request, HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) {
        String errorMessage = httpMediaTypeNotSupportedException.getMessage();
        return TarotLogUtils.messageHandler(request, TarotCode.UNSUPPORTED_MEDIA_TYPE, errorMessage, httpMediaTypeNotSupportedException);
    }

    /**
     * 捕获 500 异常处理
     * @param request   请求对象
     * @param exception Exception 500 异常
     * @author xincan
     * @date 2019/09/09 16:21
     * @return TarotResult<String>
     */
    @ExceptionHandler({Exception.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public TarotResult<String> handlerException(HttpServletRequest request, Exception exception) {
        return TarotLogUtils.messageHandler(request, TarotCode.INTERNAL_SERVER_ERROR, TarotConstantUtils.TAROT_EXCEPTION_LOG_ERROR_MESSAGE, exception);
    }

    /**
     *  捕获controller层函数单个参数校验异常
     * @param request   请求对象
     * @param constraintViolationException 参数校验异常
     * @author xincan
     * @date 2022/9/8 20:38
     * @return TarotResult<String>
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public TarotResult<String> handlerConstraintViolationException(HttpServletRequest request, ConstraintViolationException constraintViolationException) {
        Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
        if (constraintViolations.isEmpty()) {
            return TarotResult.error(TarotCode.PARAM_EXCEPTION.code(), "校验器为空");
        }
        ConstraintViolation<?> constraintViolation = constraintViolations.iterator().next();
        TarotCode tarotCode = TarotCode.PARAM_EXCEPTION;
        String message = tarotCode.message() + ": " + constraintViolation.getMessage();
        Integer code = tarotCode.code();
        String requestType = request.getMethod();
        String requestMedia = StringUtils.hasText(request.getContentType()) ? request.getContentType() : "'default;charset=" +request.getCharacterEncoding()+ "'";
        String requestUrl = request.getRequestURL().toString();
        String className = constraintViolation.getRootBeanClass().getName();
        String methodName= constraintViolation.getPropertyPath().toString().split("\\.")[0];
        String param = constraintViolation.getPropertyPath().toString().split("\\.")[1]+"="+constraintViolation.getInvalidValue();
        TarotLogUtils.printLog(message, code, requestType, requestMedia, requestUrl, className, methodName, param, constraintViolationException);
        return TarotResult.error(tarotCode.code(),message);
    }

    /**
     *  参数异常校验处理
     *  （非@RequestBody）
     * @param request   请求对象
     * @param exception BindException 属性校验异常
     * @author xincan
     * @date 2022/9/4 11:15
     * @return TarotResult<String>
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public TarotResult<String> handlerBindException(HttpServletRequest request, BindException exception) {
        List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
        FieldError fieldError = fieldErrors.get(0);
        TarotCode tarotCode = TarotCode.PARAM_EXCEPTION;
        String message = tarotCode.message() + ": " + fieldError.getDefaultMessage();
        Integer code = tarotCode.code();
        String requestType = request.getMethod();
        String requestMedia = StringUtils.hasText(request.getContentType()) ? request.getContentType() : "'default;charset=" +request.getCharacterEncoding()+ "'";
        String requestUrl = request.getRequestURL().toString();
        String className = TarotApplicationContextUtil.getClass(request);
        String methodName = TarotApplicationContextUtil.getMethod(request);
        String param = fieldError.getField() + "=" + fieldError.getRejectedValue();
        TarotLogUtils.printLog(message, code, requestType, requestMedia, requestUrl, className, methodName, param, exception);
        return TarotResult.error(tarotCode.code(), message);
    }

}
